package com.agent.vpsserver.common.utils.sign;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: gc
 * @Date: 2019/12/22
 * @Description: RSA加密工具类，含验签
 **/
public class RSAUtils {
    /**
     * 签名算法
     */
    public static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
//    public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
    /**
     * 加密算法RSA
     */
    public static final String KEY_ALGORITHM = "RSA";
    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * 获取公钥的key
     */
    private static final String PUBLIC_KEY = "RSAPublicKey";

    /**
     * 获取私钥的key
     */
    private static final String PRIVATE_KEY = "RSAPrivateKey";

    /**
     * <p>
     * 生成密钥对(公钥和私钥)
     * </p>
     *
     * @return
     * @throws Exception
     */
    public static Map<String, Object> genKeyPair() throws Exception
    {
        final KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(1024);
        final KeyPair keyPair = keyPairGen.generateKeyPair();
        final RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        final RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        final Map<String, Object> keyMap = new HashMap<String, Object>(2);
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;

    }

    /**
     * 签名字符串
     *
     * @param text
     *           需要签名的字符串
     * @param privateKey
     *           私钥(BASE64编码)
     *
     * @param charset
     *           编码格式
     * @return 签名结果(BASE64编码)
     */
    public static String sign(final String text, final String privateKey, final String charset) throws Exception
    {

        final byte[] keyBytes = Base64.decodeBase64(privateKey);
        final PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        final KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        final PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        final Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateK);
        signature.update(RSAUtils.getContentBytes(text, charset));
        final byte[] result = signature.sign();
        return Base64.encodeBase64String(result);

    }

    /**
     * 签名字符串
     *
     * @param text
     *           需要签名的字符串
     * @param sign
     *           客户签名结果
     * @param publicKey
     *           公钥(BASE64编码)
     * @param charset
     *           编码格式
     * @return 验签结果
     */
    public static boolean verify(final String text, final String sign, final String publicKey, final String charset)
            throws Exception
    {
        final byte[] keyBytes = Base64.decodeBase64(publicKey);
        final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        final KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        final PublicKey publicK = keyFactory.generatePublic(keySpec);

        final Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initVerify(publicK);
        signature.update(RSAUtils.getContentBytes(text, charset));
        return signature.verify(Base64.decodeBase64(sign));


    }

    /**
     * <P>
     * 私钥解密
     * </p>
     *
     * @param encryptedData
     *           已加密数据
     * @param privateKey
     *           私钥(BASE64编码)
     * @return
     * @throws Exception
     */
    public static byte[] decryptByPrivateKey(final byte[] encryptedData, final String privateKey) throws Exception
    {
        final byte[] keyBytes = Base64.decodeBase64(privateKey);
        final PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        final KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        final Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        final Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateK);
        final int inputLen = encryptedData.length;
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0)
        {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK)
            {
                cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
            }
            else
            {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        final byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;

    }

    /**
     * <p>
     * 公钥解密
     * </p>
     *
     * @param encryptedData
     *           已加密数据
     * @param publicKey
     *           公钥(BASE64编码)
     * @return
     * @throws Exception
     */
    public static byte[] decryptByPublicKey(final byte[] encryptedData, final String publicKey) throws Exception
    {
        final byte[] keyBytes = Base64.decodeBase64(publicKey);
        final X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        final KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        final Key publicK = keyFactory.generatePublic(x509KeySpec);
        final Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicK);
        final int inputLen = encryptedData.length;
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0)
        {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK)
            {
                cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
            }
            else
            {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        final byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;

    }

    /**
     * <p>
     * 公钥加密
     * </p>
     *
     * @param data
     *           源数据
     * @param publicKey
     *           公钥(BASE64编码)
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPublicKey(final byte[] data, final String publicKey) throws Exception
    {
        final byte[] keyBytes = Base64.decodeBase64(publicKey);
        final X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        final KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        final Key publicK = keyFactory.generatePublic(x509KeySpec);
        // 对数据加密
        final Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicK);
        final int inputLen = data.length;
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0)
        {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK)
            {
                cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
            }
            else
            {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        final byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;

    }

    /**
     * <p>
     * 私钥加密
     * </p>
     *
     * @param data
     *           源数据
     * @param privateKey
     *           私钥(BASE64编码)
     * @return
     * @throws Exception
     */
    public static byte[] encryptByPrivateKey(final byte[] data, final String privateKey) throws Exception
    {
        final byte[] keyBytes = Base64.decodeBase64(privateKey);
        final PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        final KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        final Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
        final Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateK);
        final int inputLen = data.length;
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0)
        {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK)
            {
                cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
            }
            else
            {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        final byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;

    }

    /**
     * @param content
     * @param charset
     * @return
     * @throws SignatureException
     * @throws UnsupportedEncodingException
     */
    private static byte[] getContentBytes(final String content, final String charset)
    {
        if (charset == null || "".equals(charset))
        {
            return content.getBytes();
        }
        try
        {
            return content.getBytes(charset);
        }
        catch (final UnsupportedEncodingException e)
        {
            throw new RuntimeException("签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
        }
    }

    /**
     * <p>
     * 获取私钥
     * </p>
     *
     * @param keyMap
     *           密钥对
     * @return
     * @throws Exception
     */
    public static String getPrivateKey(final Map<String, Object> keyMap) throws Exception
    {
        final Key key = (Key) keyMap.get(PRIVATE_KEY);
        return Base64.encodeBase64String(key.getEncoded());
    }

    /**
     * <p>
     * 获取公钥
     * </p>
     *
     * @param keyMap
     *           密钥对
     * @return
     * @throws Exception
     */
    public static String getPublicKey(final Map<String, Object> keyMap) throws Exception
    {
        final Key key = (Key) keyMap.get(PUBLIC_KEY);
        return Base64.encodeBase64String(key.getEncoded());
    }
    private static String privateKey = "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMvyIkE4mhXMNqVlo4MJEBHlk0ARF1DEl1A8L5gvvtxbXTLMEOHvSscjmFmWBPnZtqggWe3nDqr585lvpxEiX7pF2+c4bEBnY+BJu91y/Xm6sQBrBwvb5FHFB6A+YPXriZx5zamAsmwHlIH5zAnRhOBy6sNBlAtVFcr2mbEPuiyNAgMBAAECgYEAxS/OqOrWKD8zksVry/UGQZeLwMUFmwUJG/2lMurvCdWYjdW6eQjJbzNeSTgAhaZXscTdzmS5zNwxCGhoyutGS3TG6SqXj/uuX+3brhB3fpZwlAyAT/gfzMzx8UQUuP20rJb9hPAGuRAep/ifHX0F9Td5KUBE00zHx649JtxB5U0CQQDsXzV8oZXhs9e2nJxHE1raXfH+kD2FT53JGEZ9GQ9NooOXO8f7NUpNWSk6jXNPzX5yTQMuUN2Yd4gW5oA1WFfPAkEA3OGcnjgmbC1VkuCRpTlSfRZPyrzukXhXi0EFI61b9ZgYn7CZFq2/8R8eRAR6mvYF+9K5me4mEwgBKnI99Rqw4wJAYRXB/EzKKQgbI9AziCX0/Rf1G1/XRPgskLo9yYgPdw23CtjtDMsYl6h1fK04naCDvSLQmadxPPxrkyxdoIuGlQJBAJ5SPI+QEcJSUFciDmNOjhjI6FKaT6oHh0ByT3u+I/8KkQPNcj22NrFfK7N3PmZaV/mjlpOGBtZ3U1aYYl6QTh8CQQDhKw4WE6km+bzGfOhf5pgYrHJMQm/5I0JmsxNhr1GgTdAMY/RGiscP40wQMcrytqtfLnvxpEbgiigLicpRHmw/";
    private static String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL8iJBOJoVzDalZaODCRAR5ZNAERdQxJdQPC+YL77cW10yzBDh70rHI5hZlgT52baoIFnt5w6q+fOZb6cRIl+6RdvnOGxAZ2PgSbvdcv15urEAawcL2+RRxQegPmD164mcec2pgLJsB5SB+cwJ0YTgcurDQZQLVRXK9pmxD7osjQIDAQAB";

//    public static void main(final String[] args) throws Exception
//    {
////        final Map<String, Object> keyMap = RSAUtil.genKeyPair();
////        String privateKey = RSAUtil.getPrivateKey(keyMap);
////        String publicKey = RSAUtil.getPublicKey(keyMap);
//        System.out.println(PUBLIC_KEY + ": " + publicKey);
//        System.out.println(PRIVATE_KEY + ": " + privateKey);
//        final String testString = "{\"idNo\": \"362428199610202738\",\"idType\": \"0\",\"mobile\": \"15179687119\",\"name\": \"郭超\"}";
//
//        // 1、测试加签
//        System.out.println("=================加签测试START===================");
//        System.out.println("加签原文：" + testString);
//        final String signResult = RSAUtil.sign(testString, privateKey, "utf-8");
//        System.out.println("加签后的值：" + signResult);
//        System.out.println("=================加签测试END===================\n\n");
//
//        // 3、测试加密（公钥加密）
//        System.out.println("=================加密测试START===================");
//        System.out.println("加密前：" + signResult);
//        final byte[] data = signResult.getBytes("utf-8");
//        final byte[] encodedData = RSAUtil.encryptByPublicKey(data, publicKey);
//        System.out.println("加密后" + Base64.encodeBase64String(encodedData));
//        System.out.println("=================加密测试END===================\n\n");
//
//        // 4、测试解密（私钥解密）
//        System.out.println("=================解密测试START===================");
//        System.out.println("解密前" + Base64.encodeBase64String(encodedData));
//        final byte[] decodedData = RSAUtil.decryptByPrivateKey(encodedData, privateKey);
//        System.out.println("解密后" + new String(decodedData));
//        System.out.println("=================解密测试END===================");
//
//        // 2、测试验签
//        System.out.println("=================验签测试START===================");
//        System.out.println("验签原文：" + testString);
//        final boolean verify = RSAUtil.verify(testString, signResult, publicKey, "utf-8");
//        System.out.println("验签结果：" + verify);
//        System.out.println("=================验签测试END===================\n\n");
//        Map<String,String> map = new HashMap<>();
//        map.put("data", testString);
//        map.put("sign", Base64.encodeBase64String(encodedData));
//        System.out.println(JsonUtils.toJSON(map));
//    }

}
